package com.purchasing.system.service.impl;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.purchasing.system.exception.ResourceNotFoundException;
import com.purchasing.system.model.ERole;
import com.purchasing.system.model.Notification;
import com.purchasing.system.model.User;
import com.purchasing.system.repository.NotificationRepository;
import com.purchasing.system.repository.UserRepository;
import com.purchasing.system.service.EmailService;
import com.purchasing.system.service.NotificationService;

@Service
public class NotificationServiceImpl implements NotificationService {

    @Autowired
    private NotificationRepository notificationRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private EmailService emailService;

    @Override
    @Transactional
    public Notification sendNotification(Long userId, String title, String content, Notification.NotificationType type, Long referenceId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        
        Notification notification = new Notification();
        notification.setTitle(title);
        notification.setContent(content);
        notification.setUser(user);
        notification.setType(type);
        notification.setReferenceId(referenceId);
        notification.setCreatedAt(LocalDateTime.now());
        notification.setRead(false);
        
        Notification savedNotification = notificationRepository.save(notification);
        
        // 使用异步方法发送邮件通知
        if (user.getEmail() != null && !user.getEmail().isEmpty()) {
            emailService.sendEmailAsync(user.getEmail(), title, content);
        }
        
        return savedNotification;
    }

    @Override
    @Transactional
    public void sendNotificationToAdmins(String title, String content, Notification.NotificationType type, Long referenceId) {
        List<User> adminUsers = userRepository.findAll().stream()
                .filter(user -> user.getRoles().stream()
                        .anyMatch(role -> role.getName() == ERole.ROLE_ADMIN))
                .collect(Collectors.toList());
        
        for (User admin : adminUsers) {
            sendNotification(admin.getId(), title, content, type, referenceId);
        }
    }

    //先根据用户 ID 查询用户信息。如果用户存在，则继续执行后续逻辑；如果用户不存在，则抛出一个 ResourceNotFoundException 异常，提示用户不存在。
    @Override
    public Page<Notification> getUserNotifications(Long userId, Pageable pageable) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));//根据用户 ID 查询用户信息，并确保用户存在。如果用户不存在，则抛出一个自定义异常 ResourceNotFoundException。
        //userRepository 是一个 Spring Data JPA 的 Repository 接口
        return notificationRepository.findByUser(user, pageable);
    }


//    将整个方法的逻辑串联起来，它的执行流程如下：
//
//          查询用户信息并校验用户是否存在：
//    调用 userRepository.findById(userId) 方法，尝试从数据库中查询用户记录。
//    如果用户存在，则继续执行后续逻辑；如果用户不存在，则抛出一个 ResourceNotFoundException 异常。
//            将通知类型字符串转换为枚举类型：
//    如果传入了 typeStr（通知类型字符串），尝试将其转换为枚举类型 Notification.NotificationType。
//    如果转换失败（比如传入了一个无效的类型字符串），则忽略类型筛选条件，并记录一条日志。
//            调用 Repository 方法进行分页查询：
//    调用 notificationRepository.findByUserWithFilters(...) 方法，根据用户 ID、通知类型、已读状态、时间范围等条件分页查询通知列表。
//            返回分页结果：
//    将查询到的分页通知列表（Page<Notification>）返回给调用方。
    @Override
    public Page<Notification> getUserNotificationsWithFilters(
            Long userId, 
            String typeStr, 
            Boolean read, 
            LocalDateTime startDate, 
            LocalDateTime endDate, 
            Pageable pageable) {
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        
        // 如果传入了类型字符串，尝试转换为枚举类型
        Notification.NotificationType type = null;
        if (typeStr != null && !typeStr.isEmpty()) {
            try {
                type = Notification.NotificationType.valueOf(typeStr);//Notification.NotificationType 是一个枚举类型
            } catch (IllegalArgumentException e) {
                // 如果转换失败，忽略类型筛选
                System.out.println("无法转换通知类型: " + typeStr);
            }
        }
        
        return notificationRepository.findByUserWithFilters(
                user, 
                type, // 直接传递枚举类型，而不是字符串
                read, 
                startDate, 
                endDate, 
                pageable);
    }

    @Override
    public List<Notification> getUnreadNotifications(Long userId) {
        //返回值：List<Notification>，表示该用户的所有未读通知列表，每条记录是一个 Notification 对象
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));//orElseThrow() 是 Optional 提供的一个方法，用于处理 Optional 为空的情况。
        
        return notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, false);//这部分代码的作用是调用 NotificationRepository 中的自定义查询方法，查询指定用户的所有未读通知，并按创建时间降序排列。
    }

    @Override
    public long countUnreadNotifications(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        
        return notificationRepository.countByUserAndRead(user, false);
    }

//    这段代码是一个典型的 Service 层方法实现，具有以下特点：
//
//            防御性编程
//    在更新通知状态之前，先查询通知是否存在，并校验用户是否有权限操作该通知。
//    如果通知不存在或用户无权限，则抛出相应的异常，避免非法操作。
//            事务管理
//    使用 @Transactional 注解确保查询和更新操作的原子性，保证数据一致性。
//            清晰的职责分离
//    Service 层负责协调业务逻辑（如权限校验、状态更新）。
//    Repository 层负责具体的数据库操作（如查询通知、保存更新后的通知）。
//            安全性
//    通过校验通知关联的用户 ID 和当前用户 ID 是否一致，确保用户只能操作自己的通知，保护数据的隐私性和安全性。
//            可扩展性
//    如果需要支持批量更新通知状态，可以在 Service 层实现一个新的方法（比如 markNotificationsAsRead(List<Long> notificationIds, Long userId)）。
//    如果需要支持更复杂的权限校验（比如管理员可以操作所有用户的通知），可以扩展权限校验逻辑。
    //将指定通知标记为已读。它包含了权限校验、数据查询、状态更新等逻辑，并使用了 Spring 的事务管理功能。
    @Override
    @Transactional
    //这是 Spring 提供的一个注解，用于声明当前方法是一个事务性操作
    public Notification markNotificationAsRead(Long notificationId, Long userId) {
        Notification notification = notificationRepository.findById(notificationId)
                .orElseThrow(() -> new ResourceNotFoundException("Notification not found with id: " + notificationId));
        
        // 检查权限：只能标记自己的通知为已读
        //校验当前操作的用户是否有权限标记该通知为已读
        if (!notification.getUser().getId().equals(userId)) {
            throw new AccessDeniedException("You don't have permission to access this notification");
        }
        
        notification.setRead(true);
        return notificationRepository.save(notification);//将更新后的通知对象保存到数据库，并返回更新后的通知对象
    }

    @Override
    @Transactional
    public void markAllNotificationsAsRead(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        
        List<Notification> unreadNotifications = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, false);
        
        for (Notification notification : unreadNotifications) {
            notification.setRead(true);
            notificationRepository.save(notification);
        }
    }
} 